package damon.taskThread;

import damon.common.ConstantObject;
import damon.swing.ReadJPanel;
import damon.swing.WriteJPanel;
import damon.utils.ByteChangeUtil;
import damon.utils.BytesToData;
import damon.utils.DataToHexStrUtil;
import damon.utils.OpcUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * 该类是socket发送接收信息的类。
 * 比较重要的一个方面是该类嵌套了很多try-catch块。
 * 其中比较关键的向服务端读取数据的循环里的try块，主要分成两类：
 * 一类是会使socket连接断开的异常，该类异常是由socket发出的io流异常，该异常通过while循环外的try块处理，
 * 可以达到在io断开时，socket跳出内层业务循环，在外层连接循环中，一直尝试连接，直至连接成功！
 * 其他类异常，都会在业务循环内处理，不会使socket断开，目前包括：sql异常、运行时异常、sleep方法异常
 *
 * @author 风蚕
 */
@Getter
@Setter
public class ReadThread extends Thread {
    //    Logger logger = LoggerFactory.getLogger(ReadThread.class);
//    boolean handshake;//握手协议是否完成
    boolean runIs;//握手协议是否完成
    ReadJPanel readJPanel;

    Socket socket = null;

    public ReadThread() {
    }


//    /**
//     *
//     */
//    public ReadThread(ReadJPanel client) {
//        this.runIs = true;
//        this.readJPanel = client;
//        this.handshake = false;
//
//    }

    public ReadThread(ReadJPanel client, Socket socket) {
        this.runIs = true;
        this.readJPanel = client;
//        this.handshake = false;
        this.socket = socket;
        start();
    }

    @SneakyThrows
    @Override
    public void run() {


        OutputStream os;
        InputStream is;
//			try {
//        String ip = this.readJPanel.getIpField().getText();
        int dbNum = Integer.valueOf(this.readJPanel.getDBField().getText());
        int readLen = Integer.valueOf(this.readJPanel.getReadLenField().getText());
        int startByte = Integer.valueOf(this.readJPanel.getOffSetField().getText());
//        int port = 102;
        String readType = this.readJPanel.getTypeBox().getSelectedItem().toString();


        try {
            os = socket.getOutputStream();
            is = socket.getInputStream();
//            return;
        } catch (IOException e) {
            e.printStackTrace();
            readJPanel.append("连接断开");
            try {
                this.socket.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            readJPanel.disconnect();
            return;
        }
        try {
            //握手
           OpcUtil.handshake(is, os);
            readJPanel.append("开始读取.......");
            while (runIs) {
                byte[] dataBytes = getDataBytes(is, os, readLen, dbNum, startByte);
                byte[] plcDataBytes = new byte[readLen];
                System.arraycopy(dataBytes, 0, plcDataBytes, 0, readLen);//byte数组
                String dataBinaryStr = ByteChangeUtil.bytesTobinaryString(plcDataBytes);//二进制字符串
                //打印时间
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                readJPanel.append("time:" + sdf.format(new Date()));
                //打印二进制
                readJPanel.append("binary:" + dataBinaryStr);
//                readJPanel.append("hex:" + Hex.encodeHexString(plcDataBytes).toUpperCase());
                //根据读取的类型不同，生成不同的数据类型，并打印
                if ("chars".equals(readType)) {
                    String strData = new String(plcDataBytes);
                    readJPanel.append("chars:" + strData);
                } else if ("number".equals(readType)) {
                    char[] arr = dataBinaryStr.toCharArray();
                    if (arr[0] == '1') {
                        for (int i = 0; i < arr.length; i++) {
                            arr[i] = arr[i] == '1' ? '0' : '1';
                        }
                        String reversalDataBinaryStr = String.valueOf(arr);
                        long longData = -(Long.parseLong(reversalDataBinaryStr, 2) + 1);
                        readJPanel.append("number:" + longData);
                    } else {
                        long longData = Long.parseUnsignedLong(dataBinaryStr, 2);
                        readJPanel.append("number:" + longData);
                    }
                } else if ("number unsigned".equals(readType)) {
                    long longData = Long.parseLong(dataBinaryStr, 2);
                    readJPanel.append("number unsigned:" + longData);
                } else if ("real|lReal".equals(readType)) {
                    if (readLen == 4) {
                        float intData = BytesToData.bytesToFloat(plcDataBytes);
                        readJPanel.append("real:" + intData);
                    } else if (readLen == 8) {
                        double intData = BytesToData.bytesToDouble(plcDataBytes);
                        readJPanel.append("lReal:" + intData);
                    }
                } else {
                    readJPanel.append("未知类型:" + readType);
                    throw new Exception("未知类型！");
                }
                readJPanel.append("");
                Thread.sleep(2000);
            }
        } catch (IOException | DecoderException e) {
            e.printStackTrace();
            readJPanel.append("服务端连接断开->" + e.getMessage());
        } catch (Exception e) {
            readJPanel.append("异常导致连接断开->" + e.getMessage());
            e.printStackTrace();
        }

        try {
            if (os != null) {
                os.close();
            }
            if (is != null) {
                is.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            readJPanel.append("读取结束！");
            readJPanel.disconnect();
        }


    }


    /**
     * 获取plc返回的原始报文
     * @param is
     * @param os
     * @param readByteLeng
     * @param dbNo
     * @param startByte
     * @return
     * @throws Exception
     */
    public byte[] getDataBytes(InputStream is, OutputStream os, int readByteLeng, int dbNo, int startByte) throws Exception {
        // 根据配置参数，组装向plc请求的报文
        String sendHexMsg = createSendMsg(readByteLeng, dbNo, startByte);
        // 向plc发送读取db块请求，并获取返回数据的字节码数组
        byte[] replyBytes = OpcUtil.onceSocketCommunication(is, os, Hex.decodeHex(sendHexMsg));
//        System.out.println(replyBytes.length);
//        System.out.println(Hex.encodeHexString(replyBytes));
        // 将plc返回的字节数组，去除多余的字节，
        // 包括前部多余的字节，及实际数据后的补0字节
        int dataSizeLen = replyBytes.length - 25;//实际数据字节长度
        if(dataSizeLen==0){
            throw new RuntimeException("plc中无该配置数据，检查db 偏移量是否正确！16进制报文：" + Hex.encodeHexString(replyBytes).toUpperCase());
        }else if(dataSizeLen<0){
            throw new RuntimeException("plc返回数据异常，小于25个字节！16进制报文：" + Hex.encodeHexString(replyBytes).toUpperCase());
        }
        byte[] dataBytes = new byte[dataSizeLen];
//        try {
//
//        } catch (RuntimeException e) {
//            readJPanel.append("test");
//
//        }
        System.arraycopy(replyBytes, 25, dataBytes, 0, dataSizeLen);

        return dataBytes;

    }


    /**
     * 组装向plc写入的报文
     * @param readSize
     * @param DBNumber
     * @param startByte
     * @return
     * @throws Exception
     */
    public String createSendMsg(int readSize, int DBNumber, int startByte) throws Exception {
        String readSizeHex = DataToHexStrUtil.numToHex(readSize, 2);//读取长度
        String DBNumberHex = DataToHexStrUtil.numToHex(DBNumber, 2);//db块编号
        String startByteHex = DataToHexStrUtil.numToHex(startByte * 8, 3);//偏移量，按位
        String allMsg = "0300001F02F080320100000000000E00000401120A1002" + readSizeHex + DBNumberHex + "84" + startByteHex;
        return allMsg;
    }
}
